<?php
/**
 * System log of all events.
 *
 * @author Martin "DevelX" Jurča
 */
class Log extends Module {
    protected $name = 'Log';
    protected $administrable = true;
    protected $version = '2009-08-28';
    protected $system = true;
    protected $details = array('dependencies' => array(),
            'observes' => array('ALL', 'Log'));

    private $user = null;
    private $userLog;
    private $makeLog = true;
    private $makeUserLogs = true;
    private $deleteOldLogs = true;

    public function __construct(DBAPI $dbapi, RecordList $recordList) {
        parent::__construct($dbapi, $recordList);
        $_SESSION['logFile'] = (string) microtime(true);
        if (file_exists($this->getDir('/settings'))) {
            $content = file_get_contents($this->getDir('/settings'));
            if ($content !== false) {
                $settings = unserialize($content);
                $this->makeLog = $settings['log'];
                $this->makeUserLogs = $settings['user'];
            }
        }
    }

    public function install() {
        if (!file_exists($this->getDir('/logs'))) {
            FTP::mkdir($this->getDir('/logs', true));
        }
        FTP::chmod($this->getDir('/logs', true));
        if (!file_exists($this->getDir('/settings'))) {
            FTP::createFile($this->getDir('/settings', true));
        }
        FTP::chmod($this->getDir('/settings', true));
        $file = @fopen($this->getDir('/settings'), 'w');
        @fwrite($file, serialize(array('log' => true, 'user' => true)));
        @fclose($file);
        Modules::getModule('Users')->registerRights($this->name,
                $this->getRights());
        return true;
    }

    public function uninstall() {
        $logs = glob($this->getDir('/logs/*'));
        foreach ($logs as $log) {
            $log = substr($log, strrpos($log, '/'));
            FTP::deleteFile($this->getDir('/logs/' . $log, true));
        }
        return true;
    }

    public function initDisplay($param = null) {
        $this->setAdminAccessErrors();
    }

    public function display($param = null) {
        if (Kernel::getMode() != Kernel::ADMIN) {
            return;
        }
        if (!Modules::getModule('Users')->checkRights('Log', 'view admin')) {
            return;
        }
        if ($param == 'submenu') {
            $this->displayAdminSubmenu();
        } else {
            $details = Modules::getModule('View')->getUrl();
            if (count($details) == 1) {
                $this->displayAdminLogs();
            } elseif (count($details) == 2) {
                if ($details[1] == 'settings') {
                    $this->displayAdminSettings();
                } else {
                    $this->displayAdminUserLogs();
                }
            } else {
                $this->displayLog();
            }
        }
    }

    public function &onALL($event, &$details, $source) {
        $result = true;
        if (($event == 'login') && ($source == 'Users')) {
            $user = &Modules::getModule('Users')->getUser();
            $this->user = $user['data']['login'];
            $this->userLog = $user['data']['lastLogin'];
        }
        if ($this->user == null) {
            $this->loadUserInfo();
        }
        if ($this->makeLog) {
            if (!file_exists($this->getDir("/logs/{$_SESSION['logFile']}"))) {
                FTP::createFile($this->getDir("/logs/{$_SESSION['logFile']}",
                        true));
            }
            $log = @fopen($this->getDir("/logs/{$_SESSION['logFile']}"), 'a');
            if (!is_resource($log)) {
                return $result;
            }
            @flock($log, LOCK_EX);
            @fwrite($log, "{$this->user}:$source:$event:" .
                    serialize($details) . "\n");
            @flock($log, LOCK_UN);
            @fclose($log);
            $this->deleteOldLogFiles();
        }
        if ($this->makeUserLogs && ($this->user != '<guest>')) {
            $this->makeUserLog($event, $details, $source);
            $this->deleteOldLogFiles();
        }
        return $result;
    }

    public function &onSettings(&$details, $source) {
        $result = null;
        if (!Modules::getModule('Users')->checkRights('Log',
                'edit settings')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $this->makeLog = isset($details['log']);
        $this->makeUserLogs = isset($details['user']);
        if (!file_exists($this->getDir('/settings'))) {
            FTP::createFile($this->getDir('/settings', true));
        }
        if (!is_writable($this->getDir('/settings'))) {
            FTP::chmod($this->getDir('/settings', true));
        }
        $file = @fopen($this->getDir('/settings'), 'w');
        @fwrite($file, serialize(array('log' => $this->makeLog, 'user' =>
                $this->makeUserLogs)));
        @fclose($file);
        return $result;
    }

    public function &onDelete(&$details, $source) {
        $result = null;
        Kernel::setRedirect($this->getLink());
        if (!Modules::getModule('Users')->checkRights('Log',
                'delete logs')) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
            return $result;
        }
        $log = substr($details, 0, -1);
        FTP::deleteFile($this->getDir("/logs/$log", true));
        return $result;
    }

    /**
     * Automatically deletes all log files older than 6 months.
     *
     */
    private function deleteOldLogFiles() {
        if (!$this->deleteOldLogs) {
            return;
        }
        $logs = glob($this->getDir('/logs/*'));
        if (!is_array($logs)) {
            $this->deleteOldLogs = false;
            return;
        }
        $limit = time() - (24 * 3600 * 178);
        foreach ($logs as $log) {
            $log = pathinfo($log, PATHINFO_BASENAME);
            if (is_numeric($log)) {
                if (((int) $log) < $limit) {
                    FTP::deleteFile($this->getDir('/logs/' . $log));
                }
            } else {
                list($user, $time) = explode('-', $log);
                if (((int) $time) < $limit) {
                    FTP::deleteFile($this->getDir('/logs/' . $log));
                }
            }
        }
        $this->deleteOldLogs = false;
    }

    /**
     * Displays administration interface of module settings.
     *
     */
    private function displayAdminSettings() {
        if (!Modules::getModule('Users')->checkRights('Log',
                'edit settings')) {
            return;
        }
        $params = array('formStart' => $this->getForm('settings', true, 'Log'),
                'settings' => $this->lang('settings'), 'logEvent' =>
                $this->lang('logEvent'), 'logUsers' => $this->lang('logUsers'),
                'save' => $this->lang('save'), 'log' => $this->makeLog,
                'userLog' => $this->makeUserLogs);
        $this->template('settings', $params);
    }

    /**
     * Parses information from log and displays it.
     *
     */
    private function displayLog() {
        $details = Modules::getModule('View')->getUrl();
        $log = file_get_contents($this->getDir('/logs/' . $details[2]));
        $params = array('userLbl' => $this->lang('user'), 'moduleLbl' =>
                $this->lang('module'), 'eventLbl' => $this->lang('event'),
                'detailsLbl' => $this->lang('details'));
        if (is_numeric($details[2])) {
            if (!Modules::getModule('Users')->checkRights('Log',
                    'list logs')) {
                return;
            }
            $params['title'] = date("d.m.Y H:i:s", (int) $details[2]);
            list($params['user'], $params['module'], $params['event'],
                    $params['details']) = explode(':', $log, 4);
            $params['details'] = print_r(unserialize($params['details']),
                    true);
            $this->template('log', $params);
        } else {
            if (!Modules::getModule('Users')->checkRights('Log',
                   'list user logs')) {
                return;
            }
            list($params['user'], $params['login']) = explode('-', $details[2]);
            $params['login'] = date("d.m.Y H:i:s", $params['login']);
            $events = explode("\n", $log);
            $params['items'] = array();
            $from = null;
            $to = null;
            foreach ($events as $event) {
                if ($event != '') {
                    $item = array();
                    list($item['time'], $item['module'], $item['event'],
                            $item['details']) = explode(':', $event, 4);
                    if ($from == null) {
                        $from = (int) $item['time'];
                    }
                    $to = (int) $item['time'];
                    $item['time'] = date("d.m.Y H.i.s", $item['time']);
                    $item['details'] = print_r(unserialize($item['details']),
                            true);
                    $params['items'][] = $item;
                }
            }
            $params['totalTime'] = date("h:i:s", $to - $from);
            $this->template('userlog', $params);
        }
    }

    /**
     * Displays list of available event logs.
     *
     */
    private function displayAdminLogs() {
        if (!Modules::getModule('Users')->checkRights('Log',
                'list logs')) {
            return;
        }
        $logs = glob($this->getDir('/logs/*'));
        $logs = $logs ? $logs : array();
        $items = array();
        foreach ($logs as $id => &$log) {
            $log = pathinfo($log, PATHINFO_BASENAME);
            if (is_numeric($log)) {
                $items[] = array('time' => $log, 'link' =>
                        $this->getLink('log/' . $log . '/'), 'user' => '',
                        'delLink' => $this->getLink("$log/", 'delete'));
            }
        }
        $items = array_reverse($items);
        $params = array('logs' => $items, 'view' => $this->lang('view'),
                'delete' => $this->lang('delete'));
        $this->template('logs', $params);
    }

    /**
     * Displays submenu for administration interface.
     *
     */
    private function displayAdminSubmenu() {
        $params = array('menu' => array(array('href' => $this->getLink(),
                'title' => $this->lang('logListTitle'), 'label' =>
                $this->lang('logList')), array('href' =>
                $this->getLink('users/'), 'title' =>
                $this->lang('userListTitle'), 'label' =>
                $this->lang('userList')), array('href' =>
                $this->getLink('settings/'), 'title' =>
                $this->lang('settingsTitle'), 'label' =>
                $this->lang('settings'))));
        $this->template('submenu', $params);
    }

    /**
     * Displays logs for users.
     *
     */
    private function displayAdminUserLogs() {
        if (!Modules::getModule('Users')->checkRights('Log',
                'list user logs')) {
            return;
        }
        $logs = glob($this->getDir('/logs/*'));
        $logs = $logs ? $logs : array();
        $items = array();
        foreach ($logs as $id => &$log) {
            $log = pathinfo($log, PATHINFO_FILENAME);
            if (!is_numeric($log)) {
                list($user, $time) = explode('-', $log);
                $items[] = array('time' => $time, 'link' =>
                        $this->getLink('log/' . $log . '/'), 'user' => $user,
                        'delLink' => $this->getLink("$log/", 'delete'));
            }
        }
        $items = array_reverse($items);
        $params = array('logs' => $items, 'view' => $this->lang('view'),
                'delete' => $this->lang('delete'));
        $this->template('logs', $params);
    }

    /**
     * Logs actions of currently logged in user. Actions of pseudo-user <guest>
     * are not logged in user logs.
     *
     * @param String $event Name of the event
     * @param mixed $details Event details
     * @param String $source Source of the event
     */
    private function makeUserLog($event, &$details, $source) {
        if (!file_exists($this->getDir("/logs/{$this->user}-" .
                $this->userLog))) {
            FTP::createFile($this->getDir("/logs/{$this->user}-" .
                    $this->userLog, true));
        }
        $log = @fopen($this->getDir("/logs/{$this->user}-{$this->userLog}"),
                'a');
        if (!is_resource($log)) {
            return;
        }
        @flock($log, LOCK_EX);
        @fwrite($log, time() . ":$source:$event:" . serialize($details) .
                "\n");
        @flock($log, LOCK_UN);
        @fclose($log);
    }

    private function loadUserInfo() {
        $users = Modules::getModule('Users');
        if ($users != null) {
            $user = &$users->getUser();
            $this->user = $user['data']['login'];
            $this->userLog = $user['data']['lastLogin'];
        }
    }

    private function setAdminAccessErrors() {
        $details = Modules::getModule('View')->getUrl();
        $right = null;
        if (count($details) < 1) {
            return;
        }
        if (Modules::resolveModuleUrl($details[0]) != $this->name) {
            return;
        }
        switch (count($details)) {
            case 1:
                $right = 'list logs';
                break;
            case 2:
                if ($details[1] == 'settings') {
                    $right = 'edit settings';
                } else {
                    $right = 'list user logs';
                }
                break;
            case 3:
                $right = is_numeric($details[2]) ? 'list logs' :
                        'list user logs';
                break;
        }
        if (($right != null) && !Modules::getModule('Users')->checkRights(
                'Log', $right)) {
            Modules::getModule('View')->addMessage(
                    $this->lang('insufficentRights'), View::MSG_ERROR);
        }
    }
}
?>
